# encoding = utf-8
import traceback
import urllib
from PyQt5.QtCore import pyqtSignal, QRect, QSize, Qt, QEvent
from PyQt5.QtGui import QPixmap, QFont, QPalette, QKeyEvent
from PyQt5.QtWidgets import QApplication, \
    QWidget, \
    QPushButton, \
    QTextEdit, \
    QLabel, \
    QListWidget, QPlainTextEdit, QListWidgetItem, QFrame, QVBoxLayout, QHBoxLayout, QFileDialog, QTextBrowser
from PyQt5 import QtGui
import sys
import threading
from pyim import logger
from pyim.client import Client, ClientCallback
import urllib.request as url_request

class MessageWindow(QWidget):
    sig_close = pyqtSignal(object)
    sig_message = pyqtSignal(tuple)
    sig_resize = pyqtSignal()
    sig_refresh = pyqtSignal()

    # 用于显示每条消息的类
    class MessageItem(QWidget):
        def __init__(self, *args, **kwargs):
            super(MessageWindow.MessageItem, self).__init__(*args, **kwargs)
            self._message = None
            self._username = None

            self.lblAvatar = QLabel(self)
            self.lblUsername = QLabel(self)
            self.lblAvatar.setFrameShape(QFrame.Panel | QFrame.Sunken)
            self.lblAvatar.setScaledContents(True)
            self.lblAvatar.setFixedWidth(40)
            self.lblAvatar.setFixedHeight(40)
            pe = QPalette()
            pe.setColor(QPalette.WindowText, Qt.darkGray)
            self.lblUsername.setAutoFillBackground(True)
            self.lblUsername.setPalette(pe)

            hbox = QHBoxLayout()
            hbox.addWidget(self.lblAvatar)
            hbox.addWidget(self.lblUsername)

            self.lblMessage = QLabel(self)
            self.lblMessage.setWordWrap(True)
            self.lblMessage.adjustSize()
            vbox = QVBoxLayout()
            vbox.addLayout(hbox)
            vbox.addWidget(self.lblMessage)
            self.setLayout(vbox)

        @property
        def avatar(self):
            return self.lblAvatar.pixmap()

        @avatar.setter
        def avatar(self, avatar):
            try:
                pixmap = QPixmap()
                pixmap.loadFromData(url_request.urlopen(avatar).read())
                self.lblAvatar.setPixmap(pixmap)
            except:
                self.lblAvatar.setPixmap(QPixmap("images/user.jpeg"))

        @property
        def username(self):
            return self._username

        @username.setter
        def username(self, u):
            self._username = u
            self.lblUsername.setText(u)

        @property
        def message(self):
            return self._message

        @message.setter
        def message(self, m):
            self._message = m
            import json
            m = QTextBrowser()

            try:
                msg = json.loads(self._message)
                if msg["msg_type"] == "image":
                    url = msg["url"]
                    pixmap = QPixmap()
                    pixmap.loadFromData(url_request.urlopen(url).read())
                    self.lblMessage.setFixedSize(pixmap.size())
                    self.lblMessage.setPixmap(pixmap)
                elif msg["msg_type"] == "text":
                    m = msg["message"]
                    self.lblMessage.setText(m)
            except:
                pass
            self.lblMessage.adjustSize()
            self.adjustSize()

    def __init__(self, contact, parent, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)
        assert isinstance(parent, ContactWindow)
        self.contact = contact
        self.parent = parent
        setattr(self.parent, "_win_" + contact, self)
        self.setWindowTitle("与 " + contact + "交谈中")
        self._messages = None
        self.setGeometry(100, 100, 800, 400)
        vbox = QVBoxLayout()
        vbox.setContentsMargins(5, 5, 0, 0)
        self.historyMsgWindow = QListWidget()
        self.msgWindow = QPlainTextEdit()
        self.btnSend = QPushButton("发送")
        self.btnSend.setFixedWidth(100)
        self.btnSend.setFixedHeight(50)
        self.msgWindow.setFixedWidth(self.geometry().width() - 10)
        self.msgWindow.setFixedHeight(self.geometry().height() * 0.3)
        self.msgWindow.installEventFilter(self)
        self.historyMsgWindow.setFixedWidth(self.geometry().width() - 10)
        self.historyMsgWindow.setFixedHeight(self.geometry().height() * 0.5)

        # 设置工具栏
        hbox = QHBoxLayout()
        btnFileDlg = QPushButton()
        btnFileDlg.setFixedWidth(50)
        btnFileDlg.setFixedHeight(50)
        btnFileDlg.clicked.connect(self.slot_send_image)
        hbox.setContentsMargins(0, 0, 0, 0)
        hbox.addWidget(btnFileDlg, alignment=Qt.AlignLeft)

        vbox.addWidget(self.historyMsgWindow)
        vbox.addLayout(hbox)
        vbox.addWidget(self.msgWindow)
        vbox.addWidget(self.btnSend)

        self.setLayout(vbox)
        self.btnSend.clicked.connect(self.slot_send_message)
        self.btnSend.setShortcut(Qt.Key_Enter)
        self.sig_refresh.connect(self.slot_refresh)

    def slot_send_image(self, *args, **kwargs):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(self,
                                                  "图片",
                                                  "",
                                                  "图片 (*.jpg *.png *.jpeg);;JPG (*.jpg);;PNG (*.png)", options=options)
        import requests
        if not fileName:
            return

        response = requests.post("http://localhost:8000/im/upload/", files={
            "image": open(fileName, 'rb')
        })
        import json
        msg = json.dumps({"msg_type": "image",
                          "url": response.text})
        self.send_message(msg)

    def eventFilter(self, sender, event):
        # 这段代码是用来检测回车键是否按下。
        if isinstance(sender, QPlainTextEdit) and isinstance(event, QKeyEvent):
            if event.key() == 16777220:
                self.slot_send_message()
                return True

        return False

    def resizeEvent(self, QResizeEvent):
        self.sig_resize.emit()

    def closeEvent(self, ev):
        self.sig_close.emit(self)

    def send_message(self, msg):
        self.msgWindow.clear()
        self.msgWindow.update()
        self.sig_message.emit((self.contact, msg))
        self.slot_refresh()

    def slot_send_message(self, *args, **kwargs):
        text = self.msgWindow.toPlainText()
        if not len(text.strip()):
            return
        import json
        msg = json.dumps({"msg_type": "text",
                          "message": text})
        self.send_message(msg)

    def slot_refresh(self):
        # 当有新的消息需要显示，重新绘制窗体
        self.historyMsgWindow.clear()
        for message in self.messages:
            item = QListWidgetItem(self.historyMsgWindow)
            item.setSelected(False)
            msgItem = MessageWindow.MessageItem()
            msgItem.username = message[0]
            msgItem.avatar = ""
            msgItem.message = message[1]
            item.setSizeHint(QSize(self.historyMsgWindow.width(), msgItem.sizeHint().height()))
            self.historyMsgWindow.setItemWidget(item, msgItem)
            self.historyMsgWindow.addItem(item)

        self.historyMsgWindow.show()

    @property
    def messages(self):
        return self._messages

    @messages.setter
    def messages(self, msgs):
        self._messages = msgs
        self.slot_refresh()


class ContactWindow(QWidget, ClientCallback):
    def on_connect(self):
        pass

    def on_disconnect(self):
        pass

    sig_refresh_users = pyqtSignal()
    sig_show_avatar = pyqtSignal()

    message_windows = {}

    # 用于定制显示联系人的类
    class QContactInfo(QWidget):
        def __init__(self, username, *args, **kwargs):
            super(ContactWindow.QContactInfo, self).__init__(*args, **kwargs)
            self._username = username
            self._online = False
            self._avatar = None
            self.messages = []
            self.setFixedWidth(150)
            self.setFixedHeight(34)
            self.avatar = QLabel(self)
            self.avatar.setFrameShape(QFrame.Panel | QFrame.Sunken)
            self.avatar.setScaledContents(True)
            self.avatar.setGeometry(2, 2, 30, 30)
            self.name = QLabel(self)
            self.name.setGeometry(34, 2, 100, 34)
            self._unread_messages = 0

        # 设置在线或离线状态
        def setOnline(self, online=False):
            self._online = online
            self.setUsername(self._username, "在线" if online else "离线")

        def setUsername(self, username, comment=None):
            self._username = username
            strname = self._username
            if comment:
                strname += "(" + comment + ")"
            self.name.setText(strname)

        # 用于显示当前的未读消息条数
        def setUnreadMessage(self, flag=True):
            if flag:
                self._unread_messages += 1
                self.setUsername(self._username, "你有%d新消息" % self._unread_messages)
            else:
                self._unread_messages = 0
                self.refresh()

        # 显示头像
        def setAvatar(self, avatar):
            self._avatar = avatar
            try:
                pixmap = QPixmap()
                pixmap.loadFromData(url_request.urlopen(avatar).read())
            except:
                pixmap = QPixmap("images/user.jpeg")
            self.avatar.setPixmap(pixmap)

        def refresh(self):
            self.setAvatar(self._avatar)
            self.setOnline(self._online)

    def __init__(self, client, app, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)
        self._client = client
        self._app = app
        self._client.callback = self
        self.setWindowTitle("%s 的通信录" % self._client.username)
        self.setGeometry(100, 200, 200, 400)
        self.contacts = []
        self.contact_widgets = {}
        self.show_personal_info()
        self.show_contact_window()
        self.setFixedSize(200, 400)

    def show_personal_info(self):
        self.avatar = QLabel(self)
        self.avatar.setFrameShape(QFrame.Panel | QFrame.Sunken)
        self.avatar.setScaledContents(True)
        self.avatar.setGeometry(5, 5, 80, 80)
        self.avatar.setPixmap(QPixmap("images/user.jpeg"))

        self.name = QLabel(self)
        self.name.setGeometry(100, 20, 100, 20)
        self.name.setFont(QFont("Times", 20, QtGui.QFont.Bold))
        self.name.setText(self._client.username)

    def show_contact_window(self):
        self.viewContacts = QListWidget(self)
        rect = self.geometry()
        assert isinstance(rect, QRect)
        self.viewContacts.setGeometry(5, 100, rect.width() - 10, rect.height() - 110)
        self.viewContacts.setAutoScroll(True)
        self.viewContacts.itemDoubleClicked.connect(self.slot_show_message_window)
        self.sig_refresh_users.connect(self.slot_update_contacts)
        threading.Thread(target=self._client.list, daemon=True).start()

    def slot_update_contacts(self):
        self.viewContacts.clear()
        for u in self.contacts:
            if u["username"] == self._client.username:
                try:
                    self.avatar.pixmap().loadFromData(url_request.urlopen(u['avatar']).read())
                except:
                    pixmap = QPixmap("images/user.jpeg")
                    self.avatar.setPixmap(pixmap)
                    pass

            # 创建联系人
            item = QListWidgetItem(self.viewContacts)
            item.setSizeHint(QSize(self.viewContacts.width(), 34))
            item.setData(Qt.UserRole, u['username'])

            # 设置用户信息
            item_contact = ContactWindow.QContactInfo(u['username'])
            item_contact.setOnline(u['is_online'])
            item_contact.setAvatar(u['avatar'])

            # 下面代码用于设置定制列表窗体。
            self.contact_widgets[u['username']] = item_contact
            self.viewContacts.addItem(item)
            self.viewContacts.setItemWidget(item, item_contact)
            self.viewContacts.show()

    def on_list(self, users):
        self.contacts.extend(users)
        self.sig_refresh_users.emit()

    def on_error(self, command, errcode, errmsg):
        print(command, errcode)

    def slot_show_message_window(self, item):
        assert isinstance(item, QListWidgetItem)
        contact = item.data(Qt.UserRole)
        widget = self.contact_widgets[contact]
        widget.setUnreadMessage(False)
        widget.refresh()
        msgWindow = MessageWindow(contact, self)
        msgWindow.messages = self.viewContacts.itemWidget(item).messages
        msgWindow.sig_message.connect(self.slot_send_message)
        msgWindow.sig_close.connect(self.slot_close_message_window)
        self.message_windows[contact] = msgWindow
        msgWindow.show()

    def slot_send_message(self, msg):
        contact_info = self.contact_widgets[msg[0]]
        contact_info.messages.append((self._client.username, msg[1]))
        self._client.message(msg[0], msg[1])
        msgWindow = self.message_windows[msg[0]]
        msgWindow.sig_refresh.emit()

    def on_auth(self, user, properties):
        try:
            pixmap = QPixmap()
            pixmap.loadFromData(url_request.urlopen(properties['avatar']).read())
        except:
            pass
        else:
            self.avatar.setPixmap(pixmap)

    def on_presence(self, user, status):
        contact_widget = self.contact_widgets[user]
        contact_widget.setOnline(status)

    def on_message(self, msg):
        if "sender" not in msg:
            pass
        else:
            logger.debug("%s said to %s: %s" % (msg["sender"], msg["receiver"], msg["message"]))
            contact_widget = self.contact_widgets[msg["sender"]]
            contact_widget.messages.append((msg["sender"], msg["message"]))
            if msg["sender"] not in self.message_windows:
                contact_widget.setUnreadMessage()
            else:
                self.message_windows[msg["sender"]].sig_refresh.emit()

    def slot_close_message_window(self, window):
        try:
            del self.message_windows[window.contact]
        except Exception as e:
            pass


class LoginWindow(QWidget, ClientCallback):
    sig_show_contact_window = pyqtSignal()

    def __init__(self, app, address, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)
        self.app = app
        self.initUI()
        self._client = Client(address=address)
        self._client.callback = self
        self._client.start()

    def initUI(self):
        self.setGeometry(100, 100, 500, 300)
        self.setWindowTitle("登录")
        self.labelUsername = QLabel(self)
        self.labelUsername.setText("用户名:")
        self.labelPasswd = QLabel(self)
        self.labelPasswd.setText("密码:")

        self.labelUsername.setGeometry(50, 100, 100, 30)
        self.labelPasswd.setGeometry(50, 150, 100, 30)

        self.txtUsername = QTextEdit(self)
        self.txtUsername.setGeometry(160, 100, 300, 30)

        self.txtPasswd = QTextEdit(self)
        self.txtPasswd.setGeometry(160, 150, 300, 30)

        self.btnCancel = QPushButton(self)
        self.btnCancel.setText("取消")
        self.btnCancel.setGeometry(160, 200, 100, 50)

        self.btnLogin = QPushButton(self)
        self.btnLogin.setText("登录")
        self.btnLogin.setGeometry(310, 200, 100, 50)

        self.btnCancel.clicked.connect(self.app.quit)
        self.btnLogin.clicked.connect(self.do_login)

        self.labelStatus = QLabel(self)
        self.labelStatus.setGeometry(0, 280, 300, 20)
        self.sig_show_contact_window.connect(self.slot_show_contact_window)
        self.show()

    def slot_show_contact_window(self):
        self.labelStatus.setText("登录成功")
        self.msgWin = QtGui.QWindow()
        self.msgWin.ui = ContactWindow(client=self._client,
                                       app=self.app)
        setattr(self.msgWin.ui, "parent", self)
        self.msgWin.ui.show()
        self.close()

    def on_connect(self):
        self.labelStatus.setText("连接服务器成功")

    def on_error(self, command, errcode, errmsg):
        self.labelStatus.setText(errmsg + "(%d)" % errcode)

    def on_auth(self, username, status):
        self.sig_show_contact_window.emit()

    def do_login(self):
        username = self.txtUsername.toPlainText().strip()
        password = self.txtPasswd.toPlainText().strip()
        self._client.login(username=username, password=password)

    def on_presence(self, user, status):
        pass

    def on_message(self, msg):
        pass

    def on_list(self, users):
        pass

    def on_disconnect(self):
        pass

    def exit(self):
        self.app.quit()


app = QApplication(sys.argv)
LoginWindow(app, address=("127.0.0.1", 3000))
sys.exit(app.exec_())
